/*
 * This file (AxManager.java) is protected by the GNU GPL licence (v2). 
 * Please read the license.txt attached for further informations. 
 * 
 */

package artx.objects;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;

import artx.utils.AxLog;
import artx.utils.AxRandom;

/**
 * Basic manager class able to manage a bunch of objects by name and a unique id
 * parameter (int);
 * 
 * @author Xavier Gouchet
 * 
 */
public class AxManager {
	// TODO switch to an AxManager<T> ?

	/** Simple list of AxObjects */
	protected ArrayList<AxObject> objectList;

	/** Table mapping names to corresponding index of the {@link #objectList} */
	protected Hashtable<String, Integer> objectDict;

	/** Table mapping UID to corresponding index of the {@link #objectList} */
	protected Hashtable<Integer, Integer> objectIndex;

	/**
	 * Basic constructor.
	 * 
	 */
	public AxManager() {
		// init lists
		objectList = new ArrayList<AxObject>();
		objectDict = new Hashtable<String, Integer>();
		objectIndex = new Hashtable<Integer, Integer>();
		AxLog.Debug("AxManager::AxManager =>> initialisation of members");
	}

	/**
	 * Basic constructor.
	 * 
	 * @param path
	 *            file from which to load the manager's content
	 */
	public AxManager(String path) {
		// init lists
		objectList = new ArrayList<AxObject>();
		objectDict = new Hashtable<String, Integer>();
		objectIndex = new Hashtable<Integer, Integer>();
		AxLog.Debug("AxManager::AxManager =>> initialisation of members");
		this.GenerateFromFile(path);
	}

	/**
	 * Basic constructor.
	 * 
	 * @param path
	 *            file from which to load the manager's content
	 * @param parent
	 *            parent class from which all objects must inherit
	 */
	public AxManager(String path, Class<? extends AxObject> parent) {
		// init lists
		objectList = new ArrayList<AxObject>();
		objectDict = new Hashtable<String, Integer>();
		objectIndex = new Hashtable<Integer, Integer>();
		AxLog.Debug("AxManager::AxManager =>> initialisation of members");
		this.GenerateFromFile(path, parent);
	}

	/**
	 * Basic constructor.
	 * 
	 * @param path
	 *            file from which to load the manager's content
	 * @param parent
	 *            parent class from which all objects must inherit
	 * @param namespace
	 *            namespace of all classes in the file
	 */
	public AxManager(String path, Class<? extends AxObject> parent,
			String namespace) {
		// init lists
		objectList = new ArrayList<AxObject>();
		objectDict = new Hashtable<String, Integer>();
		objectIndex = new Hashtable<Integer, Integer>();
		AxLog.Debug("AxManager::AxManager =>> initialisation of members");
		this.GenerateFromFile(path, parent, namespace);
	}

	/**
	 * Basic constructor.
	 * 
	 * @param list
	 *            the list of objects classes
	 */
	public AxManager(String[] list) {
		// init lists
		objectList = new ArrayList<AxObject>();
		objectDict = new Hashtable<String, Integer>();
		objectIndex = new Hashtable<Integer, Integer>();
		AxLog.Debug("AxManager::AxManager =>> initialisation of members");
		this.GenerateFromList(list);
	}

	/**
	 * Basic constructor.
	 * 
	 * @param list
	 *            the list of objects classes
	 * @param parent
	 *            parent class from which all objects must inherit
	 */
	public AxManager(String[] list, Class<? extends AxObject> parent) {
		// init lists
		objectList = new ArrayList<AxObject>();
		objectDict = new Hashtable<String, Integer>();
		objectIndex = new Hashtable<Integer, Integer>();
		AxLog.Debug("AxManager::AxManager =>> initialisation of members");
		this.GenerateFromList(list, parent);
	}

	/**
	 * Basic constructor.
	 * 
	 * @param list
	 *            the list of objects classes
	 * @param parent
	 *            parent class from which all objects must inherit
	 * @param namespace
	 *            namespace of all classes in the file
	 */
	public AxManager(String[] list, Class<? extends AxObject> parent,
			String namespace) {
		// init lists
		objectList = new ArrayList<AxObject>();
		objectDict = new Hashtable<String, Integer>();
		objectIndex = new Hashtable<Integer, Integer>();
		AxLog.Debug("AxManager::AxManager =>> initialisation of members");
		this.GenerateFromList(list, parent, namespace);
	}

	/**
	 * Gets an element by its name.
	 * 
	 * @param name
	 *            name of the object to get
	 * @return the object if it exists, else null
	 */
	public AxObject GetElement(String name) {
		// check name
		if ((name == null) || (name.isEmpty())) {
			AxLog.Error("Can't get object with null or empty name.");
			return null;
		}

		// check if lists exist
		if ((objectDict == null) || (objectList == null)) {
			AxLog.Error("Objects lists have not been initialised. ");
			return null;
		}

		// get object index
		if (objectDict.containsKey(name.toLowerCase())) {
			int index = (int) objectDict.get(name.toLowerCase());
			if (index < objectList.size()) {
				return objectList.get(index);
			} else {
				AxLog.Error("Object does not exist anymore : " + name
						+ "; key removed.");
				objectDict.remove(name);
				return null;
			}
		} else {
			AxLog.Error("Unknown object : " + name);
			return null;
		}
	}

	/**
	 * Gets an element by its id.
	 * 
	 * @param id
	 *            id of the object to get
	 * @return the object if it exists, else null
	 */
	public AxObject GetElement(int id) {
		// check if lists exist
		if ((objectDict == null) || (objectList == null)) {
			AxLog.Error("Objects lists have not been initialised. ");
			return null;
		}

		// get object index
		if (objectIndex.containsKey(id)) {
			int index = (int) objectIndex.get(id);
			if (index < objectList.size()) {
				return objectList.get(index);
			} else {
				AxLog.Error("Object does not exist anymore : #" + id
						+ "; key removed.");
				objectIndex.remove(id);
				return null;
			}
		} else {
			AxLog.Error("Unknown object : #" + id);
			return null;
		}
	}

	/**
	 * Get a list of objects names.
	 * 
	 * @return the list of all objects' names
	 */
	public String[] GetNamesList() {
		if ((this.objectList == null) || (this.objectDict == null)) {
			AxLog.Error("Objects lists have not been initialised. ");
			return null;
		}
		int size = this.objectDict.size();
		Enumeration<String> names = this.objectDict.keys();
		String[] list = new String[size];

		for (int i = 0; i < size; i++) {
			list[i] = names.nextElement();
		}
		return list;
	}

	/**
	 * Gets an element randomly.
	 * 
	 * @return an object
	 */
	public AxObject GetRandomElement() {
		// check if list exist
		if (objectList == null) {
			AxLog.Error("Objects lists have not been initialised. ");
			return null;
		}

		// check if list is non empty
		if (objectList.size() == 0) {
			AxLog.Error("Objects list is empty. ");
			return null;
		}
		// get a random index
		int index = AxRandom.RandI(objectList.size());
		return objectList.get(index);
	}

	/**
	 * Register objects from a list of class names.
	 * 
	 * @param list
	 *            the list of objects classes
	 */
	public void GenerateFromList(String[] list) {
		this.GenerateFromList(list, AxObject.class, null);
	}

	/**
	 * Register objects from a list of class names.
	 * 
	 * @param list
	 *            the list of objects classes
	 * @param parent
	 *            the parent of all clas
	 */
	public void GenerateFromList(String[] list, Class<? extends AxObject> parent) {
		this.GenerateFromList(list, parent, null);
	}

	/**
	 * Register objects from a list of class names.
	 * 
	 * @param list
	 *            the list of objects classes
	 * @param parent
	 *            the parent of all classes
	 * @param namespace
	 *            the common package name for all the actuators
	 */
	public void GenerateFromList(String[] list,
			Class<? extends AxObject> parent, String namespace) {
		// check if list is empty or null
		if ((list == null) || (list.length == 0)) {
			AxLog.Info("Can't initialize the manager from an empty list");
			return;
		}

		// check if namespace is empty or null
		if ((namespace == null) || (namespace.isEmpty())) {
			namespace = "";
		} else { // check if namespace ends with a . (dot)
			if (namespace.lastIndexOf(".") < (namespace.length() - 1)) {
				namespace = namespace + ".";
			}
		}

		AxLog.Debug("AxManager::GenerateFromList =>> Reading " + list.length
				+ " element(s) with namespace \"" + namespace
				+ "\" and parent " + parent.getSimpleName());

		for (int i = 0; i < list.length; i++) {
			String name = namespace + list[i];
			AxLog.Debug("AxManager::GenerateFromList =>> Trying class " + i
					+ " / " + list.length + " : " + name);
			this.RegisterClassInstance(name, parent);
		}
	}

	/**
	 * Register objects from a list of class names read in a text file (one
	 * class per line).
	 * 
	 * @param path
	 *            the path of the list file
	 */
	public void GenerateFromFile(String path) {
		this.GenerateFromFile(path, AxObject.class, null);
	}

	/**
	 * Register objects from a list of class names read in a text file (one
	 * class per line).
	 * 
	 * @param path
	 *            the path of the list file
	 * @param parent
	 *            the parent of all classes
	 */
	public void GenerateFromFile(String path, Class<? extends AxObject> parent) {
		this.GenerateFromFile(path, parent, null);
	}

	/**
	 * Register objects from a list of class names read in a text file (one
	 * class per line).
	 * 
	 * @param path
	 *            the path of the list file
	 * @param parent
	 *            the parent of all classes
	 * @param namespace
	 *            the common package name for all the actuators
	 */
	public void GenerateFromFile(String path, Class<? extends AxObject> parent,
			String namespace) {
		// check if namespace is empty or null
		if ((namespace == null) || (namespace.isEmpty())) {
			namespace = "";
		} else { // check if namespace ends with a . (dot)
			if (namespace.lastIndexOf(".") < (namespace.length() - 1)) {
				namespace = namespace + ".";
			}
		}

		// create file reader
		File f = new File(path);
		AxLog.Debug("AxManager::GenerateFromFile =>> Reading file ("
				+ f.getAbsolutePath() + ")");
		try {
			BufferedReader input = new BufferedReader(new FileReader(f));
			String line;
			try {
				while ((line = input.readLine()) != null) {
					this.RegisterClassInstance(namespace + line, parent);
				}
			} catch (IOException e) {
				AxLog.Error("Input error when reading file " + path);
			}
		} catch (FileNotFoundException e) {
			AxLog.Error("File " + path + " not found.");
		}
	}

	/**
	 * Registers an element by instancing the specified class. The parent
	 * parameter specifies the class the object must extend to be registered
	 * 
	 * @param name
	 *            full name of the class to instantiate (package.class)
	 * @param parent
	 *            mandatory parent class of the object
	 * @return boolean whether the class has been registered
	 */
	protected boolean RegisterClassInstance(String name,
			Class<? extends AxObject> parent) {
		try {
			// get Class by name
			Class<?> objClass = Class.forName(name);

			// is class a descendant of parent
			if (!parent.isAssignableFrom(objClass)) {
				AxLog.Error("Class " + objClass.getSimpleName()
						+ " is not a child of " + parent.getSimpleName());
				return false;
			}

			// Check if class is not abstract
			int mdf = objClass.getModifiers();
			if ((mdf & 0x0400) != 0) {
				AxLog.Error("Class " + objClass.getSimpleName()
						+ " is abstract and can't be instantiated");
				return false;
			}

			// create an instance of class
			AxObject obj;
			try {
				obj = (AxObject) objClass.newInstance();
			} catch (InstantiationException e) {
				AxLog.Error("Unable to instantiate class "
						+ objClass.getSimpleName());
				return false;
			} catch (IllegalAccessException e) {
				AxLog.Error("Unable to access class "
						+ objClass.getSimpleName());
				return false;
			}

			// add to list
			this.RegisterElement(obj);
			AxLog.Debug("AxManager::RegisterClassInstance =>> class ["
					+ objClass.getSimpleName() + "] registered with name \""
					+ obj.GetName() + "\" and ID #" + obj.GetUID());
			return true;

		} catch (ClassNotFoundException e) {
			AxLog.Error("Unable to find class " + name);
			return false;
		}
	}

	/**
	 * Adds an Object to the manager (provided it has a unique name and id).
	 * 
	 * @param obj
	 *            the object to add
	 * @return true if the object has been added
	 */
	protected boolean RegisterElement(AxObject obj) {
		// check if object is not null
		if (obj == null) {
			AxLog.Error("Can't add object to manager : object is null.");
			return false;
		}

		boolean useID = false;
		boolean useName = false;

		// check if id is registerable
		if (obj.GetUID() >= 0) {
			// check if id is unique
			if (objectIndex.containsKey(obj.GetUID())) {
				AxLog.Error("Can't add object to manager : id is not unique ("
						+ obj.GetUID() + ").");
				return false;
			}
			useID = true;
		} else {
			AxLog
					.Debug("AxManager::RegisterElement =>> Object's ID is not registerable ("
							+ obj.GetName() + ":" + obj.GetUID() + ")");
		}

		// check if name is registerable
		if ((obj.GetName() != null) || (!obj.GetName().isEmpty())) {
			// check if name is unique
			if (objectDict.containsKey(obj.GetName())) {
				AxLog
						.Error("Can't add object to manager : name is not unique (\""
								+ obj.GetName() + "\").");
				return false;
			}
			useName = true;
		} else {
			AxLog
					.Debug("AxManager::RegisterElement =>> Object's name is not registerable (null)");
		}

		// check if at least one value is registerable
		if (useID || useName) {
			// if we're here, it means the object exists and is unique
			objectList.add(obj);
			int index = objectList.indexOf(obj);
			objectDict.put(obj.GetName(), index);
			if (obj.GetUID() >= 0) {
				objectIndex.put(obj.GetUID(), index);
			}
			AxLog
					.Debug("AxManager::RegisterElement =>> Object registered with name \""
							+ obj.GetName() + "\" and ID #" + obj.GetUID());
			return true;
		} else {
			AxLog.Warning("Can't add object for it has no id nor name.");
			return false;
		}
	}

	/**
	 * Checks if the given name corresponds to an object in the list.
	 * 
	 * @param name
	 *            string to check
	 * @return boolean whether an object with the given name exists
	 */
	public boolean Exists(String name) {
		AxLog.Debug("AxManager =>> Exists : checking for object with name "
				+ name);
		if ((this.objectList != null) || (this.objectList != null)) {
			if (this.objectDict.containsKey(name.toLowerCase())) {
				int index = this.objectDict.get(name.toLowerCase());
				if (index < this.objectList.size()) {
					AxObject obj = this.objectList.get(index);
					return (obj != null);
				}
			}
		}
		return false;
	}

	/**
	 * Checks if the given index corresponds to an object in the list.
	 * 
	 * @param index
	 *            string to check
	 * @return boolean whether an object with the given name exists
	 */
	public boolean Exists(int index) {
		AxLog.Debug("AxManager =>> Exists : checking for object with id "
				+ index);
		if ((this.objectList != null) || (this.objectIndex != null)) {
			if (this.objectIndex.containsKey(index)) {
				int i = this.objectIndex.get(index);
				if (i < this.objectList.size()) {
					AxObject obj = this.objectList.get(i);
					return (obj != null);
				}
			}
		}
		return false;

	}

	/**
	 * 
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		String res = "AxManager ";
		if (this.objectList != null) {
			res += "[" + this.objectList.size() + " element(s)]\n";
			int size = this.objectList.size();
			for (int i = 0; i < size; i++) {
				res += "\t" + i + " -> " + this.objectList.get(i).toString()
						+ "\n";
			}
		}
		return res;
	}

	/**
	 * Get the name of the object using the given ID.
	 * 
	 * @param i
	 * @return the name of the object
	 */
	public String GetName(int i) {
		if ((this.objectIndex != null) && (this.objectIndex.containsKey(i))) {
			int index = this.objectIndex.get(i);
			return this.objectList.get(index).GetName();
		}
		return null;
	}

	/**
	 * Get the list of objects.
	 * 
	 * @return the list of objects
	 */
	public ArrayList<AxObject> GetElements() {
		ArrayList<AxObject> result = new ArrayList<AxObject>();
		for (int i = 0; i < this.objectList.size(); i++) {
			AxObject obj = this.objectList.get(i);
			if (obj != null) {
				result.add(obj);
			}
		}
		return result;
	}

}
